import java.io.*;
import java.util.*;

/**
 * Class to generate psuedorandom
 * numbers using the Linear Congruential
 * Method. Methods are provided to generate
 * uniformly distributed ints and reals, as
 * well as normally distributed reals. The
 * values of m, a, and c are all prime, and were
 * determined with the help of Mathematica. 
 */
public class Random227
{
	private int m = 2145390523;
	private int a = 982451707;
	private int c = 48847;

	private int x; 

	//The file we are writting the
	//seed to. 
	private File file;
	
	/**
	 * Default constructor. Checks if a seed has
	 * been saved in a file. Otherwise, uses a seed
	 * generated using the system clock and creates
	 * a file. 
	 */
	public Random227()
	{
		file = new File("seed.txt");
	
		try
		{
			if(!file.exists())
			{
				seedRandom();
				file.createNewFile();
			}
			else
			{
				Scanner in = new Scanner(file);
				x = in.nextInt();
			}
		}
		//If the seed file isn't formatted correctly,
		//We default to the system clock. 
		catch(NoSuchElementException e)
		{
			System.out.println("Problem with seed file.\n Defaulting to system clock");
			seedRandom();	
		} 
		//This should never happen, since
		//we check file.exists()
		catch(FileNotFoundException e)
		{
			System.out.println("Cannot find file.");
		}
		catch(IOException e)
		{
			System.out.println("IOError occurred.");
		}
	}
	
	
	/**
	 * Creates a new Random227 object with
	 * the specified seed. 
	 *
	 * @param  seed  The seed to use. 
	 */
	public Random227(int seed)
	{
		x = seed;
	}
	
	/**
	 * Returns the value of m. 
     *
	 * @return  The value of m.
	 */
	public int getM()
	{
		return m;
	}
	
	/**
	 * Sets the value of m.
	 *
	 * @param  m  The new value of m.
	 */
	public void setM(int m)
	{
		this.m = m;
	}

	/**
	 * Returns the value of a. 
     *
	 * @return  The value of a.
	 */	
	public int getA()
	{
		return a;
	}
	
	/**
	 * Sets the value of a.
	 *
	 * @param  a  The new value of a.
	 */
	public void setA(int a)
	{
		this.a = a;
	}
	
	/**
	 * Sets the value of c.
	 *
	 * @param  c  The new value of c.
	 */	
	public int getC()
	{
		return c;
	}

	/**
	 * Sets the value of c.
	 *
	 * @param  c  The new value of c.
	 */	
	public void setC(int c)
	{
		this.c = c;
	}
	
	
	/**
	 * Writes the last value of x
	 * to a file. We only write when
	 * close is explicitely called for
	 * efficiency reasons. 
	 */
	public void close()
	{
		try
		{
			PrintWriter writer = new PrintWriter(file);
			System.out.println("Closing RNG, x: " + x);
			writer.println(x);
			writer.flush();
		}
		catch(FileNotFoundException e)
		{
			System.out.println("Cannot find file.");
		}
	
	}
	
	
	/**
	 * Seeds this RNG with the specified 
	 * int. This will erase any previously
	 * generated random number. 
	 *
	 * @param  seed  The seed to use.
	 */
	public void seedRandom(int seed)
	{
		x = seed;
	}

	
	/**
	 * Seeds this RNG with a int
	 * generated using the system
	 * clock. 
	 */
	public void seedRandom()
	{
		x = new Long(System.currentTimeMillis()).intValue();

		//Makes sure the seed is positive. (I was having
		//problems with this).
		x = Math.abs(x);
	}

	
	
	/**
	 * Returns a random int r s.t. 
	 * 0 <= r <= m - 1.
	 *
	 * @return  A random int between 0 and m - 1.
	 */
	public int nextRandom()
	{
		x = (int) (((long) a * (long) x  + (long) c) % (long) m);
		return x;
	}
	
	
	/**
	 * Returns a random int in the range
	 * [low, high).
	 *
	 * @param  low	lower limit
	 * @param  high upper limit
	 * @return  A random int between low and high.
	 */
	public int nextInt(int low, int high)
	{
		double rand = nextReal() * (high - low);
		rand += low;
		
		return new Double(Math.floor(rand)).intValue();	
	}


	/**
	 * Returns a random real number
	 * (as a double) in the range [0, 1)
	 *
	 * @return A random double between 0 and 1
	 */
	public double nextReal()
	{
		//Increment x to the
		//next random value.
		nextRandom(); 
		
		double rand = (double) x / (double) m;
	
		return rand;
	}
	
	
	/**
	 * Returns a random real number
	 * (as a double) in the range [low, high).
	 *
	 * @param  low  lower limit
	 * @param  high  upper limit.
	 * @return A random double between low and high
	 */
	public double nextFloat(double low, double high)
	{
		double rand = nextReal();
		
		return low + (high - low) * x;
	}
	
	/**
	 * Returns a normally distributed random
	 * real (as a double) with mean 0 and 
	 * standard deviation 1. 
	 *
	 * @return  A normally distributed double with 
	 *	    mean 0 and std. dev. 1. 
	 */
	public double nextGaussian()
	{
		double x1 = nextReal();
		double x2 = nextReal();
		
		return (Math.sqrt(-2 * Math.log(x1)) * Math.cos(2 * Math.PI * x2));
	}
	
	
	/**
	 * Returns a normally distributed random
	 * real (as a double) with mean m and 
	 * standard deviation s. 
	 *
	 * @return  A normally distributed double with 
	 *	    mean m and std. dev. s. 
	 */
	public double nextGaussian(double m, double s)
	{
		double rand = nextGaussian();
		return s * rand + m;
	}
	
	/**
	 * Generates a random permutation
	 * of 0 ... n-1. Uses the "modified bozo"
	 * method presented in class. 
	 * 
	 * @param  n  The size of the permutation
	 * @return  An array containing the random
	 *			permutation. 
	 */
	public int[] randomPerm(int n)
	{
		int[] p = new int[n];
		
		for(int i = 0; i < n; i++)
		{
			p[i] = i;
		}
	
		int k, temp;
		
		for(int j = 0; j < n; j++)
		{
			k = nextInt(j, n);
			
			temp = p[j];
			p[j] = p[k];
			p[k] = temp;
		}
		
		return p;
	}
}